/******************************************************************************
 * This program is a 100% Java Email Server.
 ******************************************************************************
 * Copyright (c) 2001-2011, Eric Daugherty (http://www.ericdaugherty.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******************************************************************************
 * For current versions and more information, please visit:
 * http://javaemailserver.sf.net/
 *
 * or contact the authors at:
 * java@ericdaugherty.com
 * andreaskyrmegalos@hotmail.com
 *
 ******************************************************************************
 * This program is based on the CSRMail project written by Calvin Smith.
 * http://crsemail.sourceforge.net/
 ******************************************************************************
 *
 * $Rev$
 * $Date$
 *
 ******************************************************************************/

package com.ericdaugherty.mail.server.auth;

//Java Imports
import java.io.*;
import java.util.*;
import javax.security.auth.callback.*;
import javax.security.sasl.*;

//Logging Imports
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

//Encoding Imports
import org.apache.commons.codec.binary.Base64;

//Local Imports
import com.ericdaugherty.mail.server.configuration.ConfigurationManager;
import com.ericdaugherty.mail.server.errors.InvalidAddressException;
import com.ericdaugherty.mail.server.errors.MalformedBase64ContentException;
import com.ericdaugherty.mail.server.info.EmailAddress;
import com.ericdaugherty.mail.server.info.User;
import com.ericdaugherty.mail.server.services.general.DeliveryService;

/**
 * Verify client authentication using SASL GSS-API. Possibly protect
 * the data stream using integrity/privacy wrapping.
 *
 * @author Andreas Kyrmegalos
 */
public class GSSServerMode implements AuthServerMode {

   /** Logger */
   private static final Log log = LogFactory.getLog( GSSServerMode.class );

   /** The IP address of the client */
   private String clientIp;
   private SaslServer gssSaslServer;
   private String username;
   private User user;
   private boolean isSMTPSession,domainNeeded, userMBLocked, integrity, privacy;
   private FinalizeAuthentication finalizeAuthentication;

   public GSSServerMode(Boolean isSMTPSession, String clientIp){

      this.isSMTPSession = isSMTPSession;
      if (isSMTPSession) {
         finalizeAuthentication = new FinalizeAuthenticationSMTP();
      }
      else {
         this.clientIp = clientIp;
         finalizeAuthentication = new FinalizeAuthenticationPOP3();
      }
   }

   public void negotiateGSSAuthenticationContext() throws SaslException {

      String mechanism = "GSSAPI";
      Map<String,Object> properties = new HashMap<String,Object>();
      properties.put(Sasl.QOP, ConfigurationManager.getInstance().getSaslQOP());
      String principal = ConfigurationManager.getInstance().getGSSPrincipal();
      String protocol = isSMTPSession?principal.substring(0,principal.indexOf(':')):principal.substring(principal.indexOf(':')+1,principal.indexOf('/'));
      String hostName = principal.substring(principal.indexOf('/')+1);

      gssSaslServer = Sasl.createSaslServer(mechanism,
                protocol, hostName, properties, new SASLCallbackHandler());

   }

   public byte[] evaluateResponse(byte[] responseBytes) throws SaslException {

      try {
         if (!Base64.isArrayByteBase64(responseBytes)) {
            throw new SaslException("Can not decode Base64 Content",new MalformedBase64ContentException());
         }
         byte[] token = Base64.decodeBase64(responseBytes);
         token = gssSaslServer.evaluateResponse(token);
         if(gssSaslServer.isComplete()) {
            finalizeAuthentication.finalize(username);
         }

         if (token != null) {
            return Base64.encodeBase64(token);
         }
         return new byte[0];
      }
      catch (IOException ioe) {
         throw new SaslException(ioe.getMessage(),ioe);
      }

   }

   public boolean isDomainNeeded() {
      return domainNeeded;
   }

   public boolean isUserMBLocked() {
      return userMBLocked;
   }

   public SaslServer getGssSaslServer() {
      return gssSaslServer;
   }

   private abstract class FinalizeAuthentication {
      public void finalize(String username) throws SaslException {
         
         String qop = (String)gssSaslServer.getNegotiatedProperty(Sasl.QOP);
         if (qop.equals("auth-int")) {
            integrity = true;
         }
         else if (qop.equals("auth-conf")) {
            privacy = true;
         }

      }
   }
   private class FinalizeAuthenticationSMTP extends FinalizeAuthentication {

      @Override
       public void finalize(String username) throws SaslException {
          super.finalize(username);
       }
   }
   private class FinalizeAuthenticationPOP3 extends FinalizeAuthentication {

      @Override
      public void finalize(String username) throws SaslException {
         super.finalize(username);
         DeliveryService deliveryService = DeliveryService.getDeliveryService();
         int index = username.indexOf('@');
         if (index==-1) {
            domainNeeded = true;
         }
         else {
            EmailAddress aUserAddress = null;
            try {
               aUserAddress = new EmailAddress(username.substring(0, index), username.substring(index+1));
            }
            catch (InvalidAddressException iae) {
               throw new SaslException(iae.getMessage());
            }
            user = ConfigurationManager.getInstance().getUser( aUserAddress );
            if( deliveryService.isMailboxLocked( aUserAddress ) ) {
               userMBLocked = true;
               user = null;
            }
            else {
               deliveryService.ipAuthenticated( clientIp );
               deliveryService.lockMailbox( aUserAddress );
               if( log.isInfoEnabled() ) log.info( "User: " + username + " logged in successfully.");

            }
         }
      }
   }

   public class SASLCallbackHandler implements CallbackHandler {

       public void handle(Callback[] callbacks)
             throws IOException, UnsupportedCallbackException {
          if (callbacks == null || callbacks.length==0) {
             throw new IOException("Improper callback passed");
          }
          for (int i=0;i<callbacks.length;i++) {
             if (!(callbacks[i] instanceof AuthorizeCallback)) continue;
             AuthorizeCallback acb = (AuthorizeCallback)callbacks[i];
             String auth = acb.getAuthenticationID();
             String authz = acb.getAuthorizationID();
             username = authz;
             if (auth.indexOf('@')!=-1&&authz.indexOf('@')!=-1) {
                String domain = auth.substring(auth.indexOf('@')+1).toLowerCase(Locale.ENGLISH);
                String domainz = authz.substring(authz.indexOf('@')+1).toLowerCase(Locale.ENGLISH);
                if (domain.indexOf(domainz)!=-1||domainz.indexOf(domain)!=-1) {
                   if (auth.substring(0,auth.indexOf('@')).equals(authz.substring(0,authz.indexOf('@')))) {
                      acb.setAuthorized(true);
                   }
                }
             }

          }
       }

   }

   public boolean isProtected() {
      return integrity||privacy;
   }

   public User getUser() {
      return user;
   }

   public void conclude() {
      try {
         gssSaslServer.dispose();
      } catch (SaslException ex) {
         log.debug(ex.getMessage(),ex);
      }

   }
}
